/* Copyright (c) 2008 bvproject - Gilles Guillemin  See:
    http://code.google.com/p/bvproject/

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. */

package com.bvproject.controls
{
import com.chewtinfoil.utils.StringUtils;

import flash.events.Event;
import flash.events.FocusEvent;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;

import mx.controls.TextInput;
import mx.controls.TextArea;
import flash.text.TextLineMetrics;

//--------------------------------------
//  Styles
//--------------------------------------

/**
 *  Color of the prompt when activated
 *
 *  @default 0xAAAAAA
 */
[Style(name="promptColor", type="String", format="Color", inherit="no")]
/**
 *  Style of the prompt when activated
 *
 *  @default italic
 */
[Style(name="promptStyle", type="String", enumeration="normal,italic", inherit="no")]
/**
 *  Weight of the prompt when activated
 *
 *  @default normal
 */
[Style(name="promptWeight", type="String", enumeration="normal,bold", inherit="no")]
/**
 *  Color of the required indicator when activated
 *
 *  @default 0xFF0000
 */
[Style(name="requiredColor", type="String", format="Color", inherit="no")]

//--------------------------------------
//  Other metadata
//--------------------------------------

[IconFile("../assets/SuperTextInput.png")]

/**
 *  The SuperTextInput control is an enhanced
 *  TextInput control including several missing functionalities
 * 	like a prompt function, a required indicator, etc
 * 	SuperTextInput extends TextArea control and not
 * 	TextInput and thus inherits TextArea properties
 * 	like wordWrap for example.
 *  @mxml
 *
 *
 *  <p>The <code>&lt;fc:SuperTextInput&gt;</code> tag inherits all the tag attributes
 *  of its superclass, and adds the following tag attributes:</p>
 *
 *  <pre>
 *  &lt;fc:SuperTextInput
 *    <b>Properties</b>
 * 	  prompt="<i>No default</i>"
 * 	  clearPromptOnFocus="false"
 * 	  required="false"
 * 	  startUpperCase="false"
 * 	  variableWidth="false"
 *
 * 	  <b>Styles</b>
 *    promptColor="0xAAAAAA"
 *    promptStyle="italic"
 *    promptWeight="bold"
 *    requiredColor="0xFF0000"
 *    &nbsp;
 *  /&gt;
 *  </pre>
 *
 *  @includeExample ../../../../../../docs/com/adobe/flex/extras/controls/example/AutoCompleteCountriesData/AutoCompleteCountriesData.mxml
 *
 *  @see mx.controls.TextArea
 *
 */

public class SuperTextInput extends TextArea
{
	//--------------------------------------------------------------------------
	//
	//  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     */

	public function SuperTextInput()
	{
		super();
		addEventListener(Event.CHANGE, handleTextChange);
		addEventListener(FocusEvent.FOCUS_IN, handleFocusIn);
		addEventListener(FocusEvent.FOCUS_OUT, handleFocusOut);
		addEventListener(MouseEvent.CLICK,reposition,true);
		setStyle("borderStyle", "inset");
	}

	//--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  Current version of the component version: 0.9.0.0
     */
    public static var version : String = "0.9.0.0";

	/**
     *  @private
     *  Default values for the component specific styles.
     */
	private var _promptColor : String = "#AAAAAA";
	private var _promptStyle : String = "italic";
	private var _promptWeight : String;
	private var _requiredColor : String = "#FF0000";

	/**
     *  @private
     */
	private static var hexChars:String = "0123456789ABCDEF";
	private var _delete : Boolean = false;

    //--------------------------------------------------------------------------
    //
    //  Overridden properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  displayAsPassword
    //----------------------------------

    /**
     *  @private
     */
    private var _displayAsPassword:Boolean = false;

	/**
     *  Indicates whether this control is used for entering passwords.
     *  If <code>true</code>, the field does not display entered text,
     *  instead, each text character entered into the control
     *  appears as the  character "&#42;".
     *
     *  @default false
     *  @tiptext Specifies whether to display '*'
     *  instead of the actual characters
     *  @helpid 3197
     */
	override public function set displayAsPassword(value:Boolean):void {
		_displayAsPassword = value;
		super.displayAsPassword = value;
	}

	/**
     *  @private
     */
	override public function get displayAsPassword():Boolean {
		return _displayAsPassword;
	}

	//----------------------------------
    //  text
    //----------------------------------

    /**
     *  @private
     *  Storage for the text property.
     */
    private var _text : String;
    /**
     *  @private
     */
	private var _textChanged : Boolean;
	private var _showPrompt: Boolean = true;

	[Bindable("textChanged")]
    [CollapseWhiteSpace]
    [NonCommittingChangeEvent("change")]

    /**
     *  Plain text that appears in the control.
     *  Its appearance is determined by the CSS styles of this Label control.
     *
     *  <p>Any HTML tags in the text string are ignored,
     *  and appear as  entered in the string.
     *  To display text formatted using HTML tags,
     *  use the <code>htmlText</code> property instead.
     *  If you set the <code>htmlText</code> property,
     *  the HTML replaces any text you had set using this propety, and the
     *  <code>text</code> property returns a plain-text version of the
     *  HTML text, with all HTML tags stripped out.
     *  For more information see the <code>htmlText</code> property.</p>
     *
     *  <p>To include the special characters left angle  bracket (&lt;),
     *  right angle bracket (&gt;), or ampersand (&amp;) in the text,
     *  wrap the text string in the CDATA tag.
     *  Alternatively, you can use HTML character entities for the
     *  special characters, for example, <code>&amp;lt;</code>.</p>
     *
     *  <p>If you try to set this property to <code>null</code>,
     *  it is set, instead, to the empty string.
     *  The <code>text</code> property can temporarily have the value <code>null</code>,
     *  which indicates that the <code>htmlText</code> has been recently set
     *  and the corresponding <code>text</code> value
     *  has not yet been determined.</p>
     *
     *  @default ""
     *  @tiptext Gets or sets the TextArea content
     *  @helpid 3179
     */
	override public function get text():String {
		if (_showPrompt)
		{
			return "";
		}
		return super.text;
	}

	/**
     *  @private
     */
	override public function set text(value:String):void
	{
		_text = value;
		if (value != "")
		{
			_showPrompt = false;
		}
		else
		{
			_showPrompt = true;
		}
		invalidateProperties();

		dispatchEvent(new Event("textChanged"));
	}

	//--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //----------------------------------
    //  clearPromptOnFocus
    //----------------------------------

    /**
     *  @private
     */
    private var _clearPromptOnFocus:Boolean = false;
    private var _hasFocus : Boolean = false;

    [Inspectable(category="General", defaultValue="false")]

    /**
     *  If <code>true</code>, automatically clears the prompt text
     * 	(if activated) when the focus is set on the component
     *  If <code>false</code>, the prompt text is cleared only when
     * 	the user starts typing.
     *
     *  @default false
     */
    public function get clearPromptOnFocus():Boolean
    {
        return _clearPromptOnFocus;
    }

    /**
     *  @private
     */
    public function set clearPromptOnFocus(value:Boolean):void
    {
        if (value != _clearPromptOnFocus)
        {
            _clearPromptOnFocus = value;

            invalidateProperties();
        }
    }

	//----------------------------------
    //  prompt
    //----------------------------------

    /**
     *  @private
     *  Storage for the prompt property.
     */
    private var _prompt : String = "";

    /**
     *  @private
     */
    private var promptChanged:Boolean = false;
    private var _htmlPrompt: String;

    [Inspectable(category="General")]

    /**
     *  The prompt for the SuperTextInput control. A prompt is
     *  a String that is displayed in the SuperTextInput when
     *  no text is typed or no value is affected to text
     */
    public function get prompt():String
    {
        return _prompt;
    }

    /**
     *  @private
     */
    public function set prompt(value:String):void
    {
        _prompt = value;
        promptChanged = true;
//        if (_prompt == "")
//			_showPrompt = false;
        invalidateProperties();
    }

	//----------------------------------
    //  required
    //----------------------------------

    /**
     *  @private
     */
    private var _required:Boolean = false;
    private var _requiredChanged : Boolean;

    [Bindable("requiredChanged")]
    [Inspectable(category="General", defaultValue="false")]

    /**
     *  If <code>true</code>, displays an indicator
     *  that the SuperTextInput control requires user input.
     *  If <code>false</code>, indicator is not displayed.
     *
     *  <p>This property controls the indicator display only.
     *  You must attach a validator to the SuperTextInput
     *  control if you require input validation.</p>
     *
     *  @default false
     */
    public function get required():Boolean
    {
        return _required;
    }

    /**
     *  @private
     */
    public function set required(value:Boolean):void
    {
        _required = value;
		_requiredChanged = true;
        invalidateProperties();
        dispatchEvent(new Event("requiredChanged"));
    }

    //----------------------------------
    //  startUpperCase
    //----------------------------------

    /**
     *  @private
     */
    private var _startUpperCase : Boolean;
	private var _startUpperCaseChanged : Boolean;

	/**
     *  If <code>true</code>, the first letter
     * 	(if it's an alphabetic input) is automatically
     * 	converted to uppercase.
     * 	Particularly useful for user forms.
     *
     *  @default false
     */
    public function get startUpperCase():Boolean
    {
        return _startUpperCase;
    }

	/**
     *  @private
     */
    public function set startUpperCase(value:Boolean):void
    {
        if (value != _startUpperCase)
        {
            _startUpperCase = value;
            _startUpperCaseChanged = true;

            invalidateProperties();
        }
    }

	//----------------------------------
    //  variableWidth
    //----------------------------------

    /**
     *  @private
     */
    private var _variableWidth:Boolean = false;

    [Inspectable(category="General", defaultValue="false")]

    /**
     *	(Experimental)
     *  If <code>true</code>, the SuperTextInput control
     *  automatically expands if the entered text
     * 	overrides the limits of the component.
     *
     *  @default false
     */
    public function get variableWidth():Boolean
    {
        return _variableWidth;
    }

    /**
     *  @private
     */
    public function set variableWidth(value:Boolean):void
    {
        if (value != _variableWidth)
        {
            _variableWidth = value;

            invalidateProperties();
        }
    }

	//--------------------------------------------------------------------------
    //
    //  Overridden methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
	override protected function commitProperties():void
	{
		if (promptChanged || _requiredChanged)
		{
			promptChanged = false;
			_requiredChanged = false;
			_htmlPrompt = _prompt;
			if (_promptStyle == "italic")
				_htmlPrompt = "<i>"+_htmlPrompt+"</i>";

			if (_promptStyle == "bold")
				_htmlPrompt = "<b>"+_htmlPrompt+"</b>";

			_htmlPrompt = "<FONT COLOR='"+_promptColor+"'>"+_htmlPrompt+"</FONT>";

			if (_required)
				_htmlPrompt = _htmlPrompt+"<FONT COLOR='"+_requiredColor+"'> *</FONT>";
		}

		if (_showPrompt)
		{
			if ((!clearPromptOnFocus) || (clearPromptOnFocus && !_hasFocus))
			{
				super.htmlText = _htmlPrompt;
				super.displayAsPassword = false;
			}
			else
			{
				super.htmlText = "<FONT COLOR='"+getStyle("color")+"'></FONT>";
			}
		}
		else
		{
			setStyle("color", getStyle("color"));
			super.htmlText = _text;
			_showPrompt = false;
			if(super.displayAsPassword != _displayAsPassword) {
				super.displayAsPassword = _displayAsPassword;
			}
		}

		super.commitProperties();
	}

	/**
     *  @private
     */
	override protected function measure():void
    {
        super.measure();
        // Reduces the original minimum height of the textArea from 2 to 1 line
        measuredMinHeight = measuredHeight = DEFAULT_MEASURED_MIN_HEIGHT;
        measuredWidth = DEFAULT_MEASURED_WIDTH;

        var lineMetrics:TextLineMetrics = measureText(text);

        if (measuredWidth < lineMetrics.width + 10 && _variableWidth)
            measuredWidth = measuredMinWidth = lineMetrics.width + 10;
    }

	/**
     *  @private
     */
	override public function styleChanged(styleProp:String):void
	{
		super.styleChanged(styleProp);

		if (getStyle("promptColor") != null)
		{
			_promptColor = toHex(getStyle("promptColor"));
		}
		if (getStyle("promptStyle") != null)
		{
			_promptStyle = getStyle("promptStyle");
		}
		if (getStyle("promptWeight") != null)
		{
			_promptWeight = getStyle("promptWeight");
		}
		if (getStyle("requiredColor") != null)
		{
			_requiredColor = toHex(getStyle("requiredColor"));
		}

	}

	//--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

     /**
     *  @private
     *  Converts an integer to it's hexadecimal value.
     *
     *  @param n the integer to convert.
     *
     * 	@return for example #FFFFFF
     */
	private static function toHex( n:int ):String {
		var s:String = "";

		for ( var i:int = 0; i < 4; i++ ) {
			s += hexChars.charAt( ( n >> ( ( 3 - i ) * 8 + 4 ) ) & 0xF )
				+ hexChars.charAt( ( n >> ( ( 3 - i ) * 8 ) ) & 0xF );
		}

		s = "#"+s.substr(2, 7);

		return s;
	}

	//--------------------------------------------------------------------------
    //
    //  Overridden event handlers: UIComponent
    //
    //--------------------------------------------------------------------------

	/**
     *  @private
     * 	Intercepts the DELETE and RIGHT arrow keys
     * 	to automatically reposition the cursor
     * 	at the beginning  of the SuperTextInput control
     * 	if no text was entered but the prompt text
     * 	was activated.
     */
	override protected function keyDownHandler(event:KeyboardEvent):void
	{
	    super.keyDownHandler(event);

		if (_showPrompt)
		{
			if( event.keyCode == Keyboard.RIGHT) {
				super.setSelection(0,0);
			}
		}

		_delete = false;
		if( event.keyCode == Keyboard.DELETE) {
			_delete = true;
		}
	}

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

	/**
     *  @private
     * 	Automatically repositions the cursor at the beginning
     * 	of the SuperTextInput control when the focus is set
     * 	if no text was entered but the prompt text
     * 	was activated.
     */
	private function handleFocusIn(event: FocusEvent): void {
		_hasFocus = true;
		if (_showPrompt) {
			super.setSelection(0,0);
		}
	}

	/**
     *  @private
     * 	Reinstates the prompt when the SuperTextInput control
     *  loses the focus if the clearPromptOnFocus
     * 	property is <code>true</code>
     */
	private function handleFocusOut(event: FocusEvent): void {
		_hasFocus = false;
		if (clearPromptOnFocus)
		{
			invalidateProperties();
		}
	}

	/**
     *  @private
     * 	Captures the user's input and handle whether
     * 	or not to show the prompt text or the user's
     * 	input.
     */
	private function handleTextChange(event: Event): void
	{
		_showPrompt = false;
		var tmpPrompt : String = _prompt;

		if (_delete)
			tmpPrompt = _prompt.substr(1, _prompt.length);

		if (StringUtils.remove(super.text, tmpPrompt) == "")
		{
			_showPrompt = true;
		}

		_text = super.text = StringUtils.remove(super.text, tmpPrompt+(_required ? " *" : ""));

		if (_startUpperCase)
		{
			_text = super.text = StringUtils.capitalize(_text);
		}

		invalidateProperties();
	}

	/**
     *  @private
     */
	private function reposition (event:MouseEvent):void
	{
		if (_showPrompt)
		{
			super.setSelection(0,0);
		}
	}

}
}